package spring.cloud.tasks.executor.task;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import spring.cloud.tasks.executor.api.ReturnCode;
import spring.cloud.tasks.executor.api.TaskExecuteResultGroup;
import spring.cloud.tasks.executor.api.TaskItemExecuteContext;
import spring.cloud.tasks.executor.utils.Constant;
import spring.cloud.tasks.executor.utils.log.LogUtils;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Setter
@Getter
public class TaskItemExecute {
    protected static final int INIT = 0;
    protected static final int TIMEOUT = 1;
    protected static final int SUCCESS = 2;
    protected static final int FORCE_STOP = 3;
    protected static final int STOPPED = 4;
    protected final String taskId;
    protected final TaskExecuteEngine taskExecuteEngine;
    protected final TaskExecuteContext taskExecuteContext;
    protected final Integer taskItemIndex;
    protected final String taskItemValue;
    //
    protected final int timeoutSeconds; // second
    protected TaskItemExecuteResult taskExecuteResult;
    //
    protected long startTime;
    protected long endTime;
    protected boolean hasTaskExecuteResult = false;
    protected Thread currentThread;
    protected AtomicInteger status = new AtomicInteger(INIT);
    protected boolean breakForceStop = false;
    protected TaskItemExecuteContext taskExecutionContext;

    public TaskItemExecute(String taskId, TaskExecuteEngine taskExecuteEngine, Integer taskItemIndex, String taskItemValue, int timeoutSeconds, TaskExecuteContext taskExecuteContext) {
        //
        this.taskId = taskId;
        this.taskExecuteEngine = taskExecuteEngine;
        this.taskExecuteContext = taskExecuteContext;
        this.taskItemIndex = taskItemIndex;
        this.taskItemValue = taskItemValue;
        this.timeoutSeconds = timeoutSeconds;
    }

    public long getExecutionTime() {
        return endTime - startTime;
    }

    /**
     * 生成分片上下文对象
     */
    public TaskItemExecuteContext getTaskItemExecuteContext() {
        if (taskExecutionContext == null) {
            if (taskExecuteContext == null) {
                return null;
            }
            TaskItemExecuteContext taskItemExecuteContext = new TaskItemExecuteContext();
            taskItemExecuteContext.setTaskId(taskExecuteContext.getTaskId());
            taskItemExecuteContext.setTaskItemTotalCount(taskExecuteContext.getTaskItemTotalCount());
            taskItemExecuteContext.setTaskItemIndexList(taskExecuteContext.getTaskItemIndexList());
            taskItemExecuteContext.setTaskItemToTaskItemAndParameterMap(taskExecuteContext.getTaskItemIndexToTaskItemAndParameterMap());
            taskItemExecuteContext.setTaskParameter(taskExecuteContext.getTaskParameter());
            taskItemExecuteContext.setCustomContext(taskExecuteContext.getCustomContext());
            this.taskExecutionContext = taskItemExecuteContext;
        }
        return taskExecutionContext;
    }

    /**
     * 设置该分片的状态为TIMEOUT
     *
     * @return Mark timeout success or fail
     */
    public boolean setTimeout() {
        return status.compareAndSet(INIT, TIMEOUT);
    }

    /**
     * 该分片执行是否TIMEOUT
     */
    public boolean isTimeout() {
        return status.get() == TIMEOUT;
    }

    /**
     * 设置该分片的状态为FORCE_STOP
     */
    public boolean forceStop() {
        return status.compareAndSet(INIT, FORCE_STOP);
    }

    /**
     * 作业执行是否被中止
     */
    public boolean isBreakForceStop() {
        return breakForceStop;
    }

    /**
     * 该分片是否FORCE_STOP状态
     */
    public boolean isForceStop() {
        return status.get() == FORCE_STOP;
    }

    /**
     * 是否成功
     */
    public boolean isSuccess() {
        return status.get() == SUCCESS;
    }

    /**
     * 重新初始化
     */
    public void reset() {
        status.set(INIT);
        breakForceStop = false;
        taskExecuteResult = null;
        hasTaskExecuteResult = false;
    }

    /**
     * 执行前回调
     */
    public void beforeExecute() {
        this.startTime = System.currentTimeMillis();
    }

    public TaskItemExecuteResult execute() throws Throwable {
        return taskExecuteEngine.execute(taskId, taskExecuteContext, taskItemIndex, taskItemValue, this);
    }

    /**
     * 执行后回调
     */
    public void afterExecution() {
        this.endTime = System.currentTimeMillis();
    }

    /**
     * 真正执行作业分片逻辑
     *
     * @return 执行结果
     */
    public TaskItemExecuteResult call() {
        reset();


        currentThread = Thread.currentThread();
        TaskItemExecuteResult executeResult = null;
        try {

            beforeExecute();

            executeResult = execute();

            // 在此之后，不能再强制停止本线程
            breakForceStop = true;
        } catch (Throwable t) {
            // 在此之后，不能再强制停止本线程
            breakForceStop = true;

            // 不是超时，不是强制停止。 打印错误日志，设置SaturnJobReturn。
            if (status.get() != TIMEOUT && status.get() != FORCE_STOP) {
                LogUtils.error(log, taskId, t.toString(), t);
                executeResult = new TaskItemExecuteResult(ReturnCode.SYSTEM_FAIL, t.getMessage(),
                        TaskExecuteResultGroup.FAIL);
            }

        } finally {
            if (status.compareAndSet(INIT, SUCCESS)) {
                this.taskExecuteResult = executeResult;
            }

            if (taskExecuteEngine != null && taskExecuteEngine.getTaskConfigurationService().showNormalLog()) {
                String jobLog = null;
                if (jobLog != null && jobLog.length() > Constant.MAX_JOB_LOG_DATA_LENGTH) {
                    LogUtils.info(log, taskId,
                            "As the job log exceed max length, only the previous {} characters will be reported",
                            Constant.MAX_JOB_LOG_DATA_LENGTH);
                    jobLog = jobLog.substring(0, Constant.MAX_JOB_LOG_DATA_LENGTH);
                }

                this.taskExecuteContext.putTaskItemLog(this.taskItemIndex, jobLog);
            }
        }

        return this.taskExecuteResult;
    }

    protected void checkAndSetSaturnJobReturn() {
        switch (status.get()) {
            case TIMEOUT:
                taskExecuteResult = new TaskItemExecuteResult(ReturnCode.SYSTEM_FAIL,
                        "execute job timeout(" + timeoutSeconds * 1000 + "ms)", TaskExecuteResultGroup.TIMEOUT);
                break;
            case FORCE_STOP:
                taskExecuteResult = new TaskItemExecuteResult(ReturnCode.SYSTEM_FAIL, "the job was forced to stop",
                        TaskExecuteResultGroup.FAIL);
                break;
            case STOPPED:
                taskExecuteResult = new TaskItemExecuteResult(ReturnCode.SYSTEM_FAIL,
                        "the job was stopped, will not run the business code", TaskExecuteResultGroup.FAIL);
                break;
            default:
                break;
        }
        if (taskExecuteResult == null) {
            taskExecuteResult = new TaskItemExecuteResult(ReturnCode.USER_FAIL,
                    "the SaturnJobReturn can not be null", TaskExecuteResultGroup.FAIL);
        }
    }

    public void beforeTimeout() {
        try {
            taskExecuteEngine.beforeTimeout(taskId, taskItemIndex, taskItemValue, taskExecuteContext, this);
        } catch (Throwable t) {
            LogUtils.error(log, taskId, t.toString(), t);
        }
    }

    protected void onTimeout() {
        try {
            taskExecuteEngine.postTimeout(taskId, taskItemIndex, taskItemValue, taskExecuteContext, this);
        } catch (Throwable t) {
            LogUtils.error(log, taskId, t.toString(), t);
        }
    }

    public void beforeForceStop() {
        try {
            taskExecuteEngine.beforeForceStop(taskId, taskItemIndex, taskItemValue, taskExecuteContext, this);
        } catch (Throwable t) {
            LogUtils.error(log, taskId, t.toString(), t);
        }
    }

    protected void postForceStop() {
        try {
            taskExecuteEngine.postForceStop(taskId, taskItemIndex, taskItemValue, taskExecuteContext, this);
        } catch (Throwable t) {
            LogUtils.error(log, taskId, t.toString(), t);
        }
    }

}
